package com.central.gateway.config;

import com.central.gateway.auth.*;
import com.central.oauth2.common.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.authentication.ServerAuthenticationEntryPointFailureHandler;

/**
 * 首先要了解一下基本的知识点，
 * OAuth(Open Authorization)是一个开放标准，允许用户让第三方应用访问该用户在某一网站上存储的私密的资源（如照片，视频，联系人列表），
 * 而无需将用户名和密码提供给第三方应用。
 * 这里注意几个点：是第三方应用去访问用户的私有资源，不需要提供用户的账号密码给第三方网站。
 * <p>
 * Spring-Security OAuth 2.0 中有几个关键的角色：<p>
 * 1. 资源服务器（一般是正常的 api 服务器）<p>
 * 2. 授权服务器（uaa，授权服务器）<p>
 * 3. 资源所有者（一般就是用户）<p>
 * 4. 客户端（前端应用）<p>
 * <p>
 * OAuth 2.0 运行流程：
 * +--------+                               +-----------------+
 * |        |--（A）------- 授权请求 -------->|                 |
 * |        |                               | 资源所有者（用户） |
 * |        |<-（B）------- 授权许可 ---------|                 |
 * |        |                               +-----------------+
 * |        |
 * |        |                               +-----------------+
 * |        |--（C）------- 授权许可 -------->|                 |
 * | 客户端  |                               |  授权服务器（1    |
 * |        |<-（D）----- Access Token ----）|                 |
 * |        |                               +-----------------+
 * |        |
 * |        |                               +-----------------+
 * |        |（-（E）---- Access Token ----->|                 |
 * |        |                               |   资源服务器（2   |
 * |        |<-（F）---- 获取受保护的资源 -----|                 |
 * +--------+                               +-----------------+
 * 当用户在我们的网站或者 app 上访问某个资源（图片，文章，视频）的时候，假设资源需要用户登录或者其他必要信息（不开放给游客的），比如网站的付费视频，
 * 用户点击播放按钮的时候，客户端开始要验证用户信息，开始流程：
 * A：网站提示用户授权（未登录的要登录），想一下我们使用微信登录第三方网站，我们在扫码之后，还要授权。
 * <p>
 * B：用户登录了，在授权页面授权。***关键点（用户如何给客户端进行授权）
 * C：client 用授权许可去向我们的授权服务器（uaa 模块）去请求 access_token。
 * D：uaa 认证信息，正确后返回 access_token 信息给 client。
 * E：client 得到了 access_token 存储在本地，然后每次访问资源的时候都带上这个 token。
 * F：资源服务器，接收到访问请求，并检查 header 有 token 信息，验证（这个验证应该是他自己去和 uaa 通信一次）一下 token 正确后返回资源（视频流）。
 * <p>
 * 用户如何给客户端进行授权？
 * 【注意认证和授权的区别，认证可以理解为登录，授权就是进一步得到登录用户的拥有的权限】
 * OAuth2.0 定义了四种授权方式：
 * 1. 授权码模式（Authorization Code）
 * 最安全的，微信的授权获取用户信息的方式就是这种。
 * 2. 密码模式（Resource Owner Password Credentials）
 * 用户向 client 提供 username/password，client 使用这些信息向 uaa 索要授权。这个模式下要求 client 必须是受信任的，否则会泄漏信息，
 * 自有 app，自有桌面应用，自有网站等可以
 * 3. 简化模式（Implicit）
 * <p>
 * 4. 客户端模式（Client Credentials）
 *
 * <p>
 * 要实现 oauth 服务，需要有一个统一的授权服务器，就是 my-uaa 模块，{@link com.central.oauth.config.AuthorizationServerConfig}
 * 开启 @EnableAuthorizationServer 注解即可，当然还要配合 SecurityConfig 加个配置。
 * <p>
 * 然后就是资源服务器（@EnableResourceServer注解标记），资源服务器其实就是我们的各种 api 服务器。我们直接在网关拦截，作为资源服务器即可。
 * 这里使用了 sc-gateway 和 zuul-gateway
 * <p>
 * Spring-security OAuth2.0 资源服务器配置
 */
@Configuration
public class ResourceServerConfiguration {

    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private PermissionAuthManager permissionAuthManager;

    @Bean
    SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        //认证处理器
        ReactiveAuthenticationManager customAuthenticationManager = new CustomAuthenticationManager(tokenStore);
        JsonAuthenticationEntryPoint entryPoint = new JsonAuthenticationEntryPoint();
        //token转换器
        ServerBearerTokenAuthenticationConverter tokenAuthenticationConverter = new ServerBearerTokenAuthenticationConverter();
        tokenAuthenticationConverter.setAllowUriQueryParameter(true);
        //oauth2认证过滤器
        AuthenticationWebFilter oauth2Filter = new AuthenticationWebFilter(customAuthenticationManager);
        oauth2Filter.setServerAuthenticationConverter(tokenAuthenticationConverter);
        oauth2Filter.setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler(entryPoint));
        // 成功认证，将clientid注册为tenantid
        oauth2Filter.setAuthenticationSuccessHandler(new Oauth2AuthSuccessHandler());
        http.addFilterAt(oauth2Filter, SecurityWebFiltersOrder.AUTHENTICATION);

        ServerHttpSecurity.AuthorizeExchangeSpec authorizeExchange = http.authorizeExchange();
        if (securityProperties.getAuth().getHttpUrls().length > 0) {
            authorizeExchange.pathMatchers(securityProperties.getAuth().getHttpUrls()).authenticated();
        }
        if (securityProperties.getIgnore().getUrls().length > 0) {
            authorizeExchange.pathMatchers(securityProperties.getIgnore().getUrls()).permitAll();
        }
        authorizeExchange
                .pathMatchers(HttpMethod.OPTIONS).permitAll()
                .anyExchange()
                .access(permissionAuthManager)
                .and()
                .exceptionHandling()
                // 拒绝访问的handler
                .accessDeniedHandler(new JsonAccessDeniedHandler())
                .authenticationEntryPoint(entryPoint)
                .and()
                .headers()
                .frameOptions()
                .disable()
                .and()
                // 禁用基础认证方式
                .httpBasic().disable()
                .csrf().disable();
        return http.build();
    }
}
